﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Reflection;
using System.Collections.ObjectModel;
using Kokomo.Mirror.CliMetadata;
using System.ComponentModel;
using System.Threading;

namespace Kokomo.PeInspector.Reflection
{
	/// <summary>
	/// Represents an assembly list.
	/// </summary>
	/// <remarks>
	/// This class is used by <see cref="AssemblyListDocumentViewModel"/>
	/// to serialize an assembly list document.
	/// </remarks>
	public class AssemblyList
	{
		public AssemblyList()
		{
			this._assemblies = new ObservableCollection<Assembly>();
		}

		private bool _isAssembliesValid;
		private ObservableCollection<Assembly> _assemblies;
		[XmlIgnore]
		public IList<Assembly> Assemblies
		{
			get
			{
				return this._assemblies;
			}
		}

		private IAssemblyLoadContext _loadContext;
		/// <summary>
		/// Gets the <see cref="IAssemblyLoadContext"/> used to load assemblies.
		/// </summary>
		/// <seealso cref="Attach"/>
		[XmlIgnore]
		public IAssemblyLoadContext LoadContext
		{
			get { return this._loadContext; }
		}

		/// <summary>
		/// Attaches this object to a <see cref="IAssemblyLoadContext"/>.
		/// </summary>
		/// <param name="loadContext">The <see cref="IAssemblyLoadContext"/> to use.</param>
		/// <remarks>
		/// When attaching, assemblies listed in <see cref="AssemblyFileNames"/>
		/// are loaded.
		/// This method can only be called once.
		/// </remarks>
		/// <seealso cref="LoadContext"/>
		public void Attach(IAssemblyLoadContext loadContext)
		{
			if (loadContext == null) throw new ArgumentNullException("loadContext");

			this._loadContext = loadContext;

			if (this._assemblyFileNames != null)
				this.LoadAssemblies(this._assemblyFileNames);
		}

		public void AttachAsync(IAssemblyLoadContext loadContext)
		{
			new Thread(() => this.Attach(loadContext)).Start();
		}

		[XmlIgnore]
		public bool IsAttached { get { return this._loadContext != null; } }

		private void LoadAssemblies(string[] fileNames)
		{
			foreach (var fileName in fileNames)
			{
				Assembly assembly = this.LoadContext.LoadAssemblyFile(fileName);
				this.Assemblies.Add(assembly);
			}
		}

		public bool AddAssembly(Assembly assembly)
		{
			bool contains;
			lock (this._assemblies)
			{
				contains = this._assemblies.Contains(assembly);
				if (!contains)
					this._assemblies.Add(assembly);
			}

			return !contains;
		}

		public void AddAssemblyWithReferences(Assembly assembly)
		{
			if (assembly == null) throw new ArgumentNullException("assembly");

			bool added = this.AddAssembly(assembly);
			if (!added) return;

			try
			{
				foreach (var referencedAssemblyName in assembly.GetReferencedAssemblies())
				{
					try
					{
						Assembly referencedAssembly;
						//string assemblyPath = this.GetAssemblyPath(referencedAssemblyName);
						//if (assemblyPath != null)
						//    referencedAssembly = _AssemblyLoadFrom(assemblyPath);
						//else
						referencedAssembly = this.LoadContext.LoadAssembly(referencedAssemblyName, assembly);

						if (referencedAssembly != null)
							this.AddAssemblyWithReferences(referencedAssembly);
					}
					catch { }
				}
			}
			catch { }
		}

		public void AddAssemblyWithReferencesAsync(Assembly assembly)
		{
			new Thread(() => this.AddAssemblyWithReferences(assembly)).Start();
		}

		private string[] _assemblyFileNames;
		/// <summary>
		/// Gets or sets an array of assembly file names.
		/// </summary>
		[XmlArray("Assemblies")]	// Compatibility with older versions
		[EditorBrowsable(EditorBrowsableState.Never)]
		public string[] AssemblyFileNames
		{
			get { return this.Assemblies.Select(a => a.Location).ToArray(); }
			set
			{
				if (this.IsAttached && value != null)
					this.LoadAssemblies(value);
				else
					this._assemblyFileNames = value;
			}
		}

		/// <summary>
		/// Gets or sets an array of <see cref="AssemblyMemberRef"/>s
		/// marked as favorites.
		/// </summary>
		public AssemblyMemberRef[] Favorites { get; set; }
	}
}
